﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using Opennav.MapGenerator.Geometry;

namespace Opennav.MapGenerator.OSM
{
    public class OSMSequentialReader
    {
        Stream input;
        XmlTextReader rdr;

        public OSMSequentialReader(string osmFile) : this(new FileStream(osmFile, FileMode.Open)) { }

        public OSMSequentialReader(Stream osmInputStream)
        {
            this.input = osmInputStream;
            this.rdr = new XmlTextReader(osmInputStream);
        }

        public void Close()
        {
            rdr.Close();
        }

        public long Position
        {
            get { return input.Position; }
        }

        public long Length
        {
            get { return input.Length; }
        }

        public OSMEntity ReadNext()
        {
            while (rdr.Read())
            {
                if (rdr.NodeType == XmlNodeType.Element)
                {
                    if (rdr.Name == "node")
                    {
                        bool hasElements = !rdr.IsEmptyElement;
                        double longitude = 0.0f;
                        double latitude = 0.0f;
                        bool? visible = null;
                        uint id = 0;
                        for (int attr = 0; attr < rdr.AttributeCount; attr++)
                        {
                            rdr.MoveToAttribute(attr);
                            if (rdr.Name == "id")
                                if (rdr.Value.StartsWith("-"))
                                    id = (uint)(uint.MaxValue + int.Parse(rdr.Value));
                                else
                                    id = uint.Parse(rdr.Value);
                            else if (rdr.Name == "lat")
                                latitude = double.Parse(rdr.Value.Replace(".", ","));
                            else if (rdr.Name == "lon")
                                longitude = double.Parse(rdr.Value.Replace(".", ","));
                            else if (rdr.Name == "visible")
                                if (rdr.Value == "true")
                                    visible = true;
                                else
                                    visible = false;
                        }
                        List<OSMTag> tags = new List<OSMTag>();
                        if (hasElements)
                        {
                            while (rdr.Read() && !(rdr.NodeType == XmlNodeType.EndElement && rdr.Name == "node"))
                            {
                                if (rdr.Name == "tag")
                                {
                                    if (rdr.AttributeCount != 2)
                                        throw new Exception("wrong attribute count in tag element");
                                    string key = null;
                                    string value = null;
                                    for (int attr = 0; attr < 2; attr++)
                                    {
                                        rdr.MoveToAttribute(attr);
                                        if (rdr.Name == "k")
                                            key = rdr.Value;
                                        else if (rdr.Name == "v")
                                            value = rdr.Value;
                                    }
                                    tags.Add(new OSMTag() { Key = key, Value = value });
                                }
                            }
                        }
                        if (visible == null || visible == true)
                        {
                            OSMNode node = new OSMNode() { Id = id, Coordinate = Coordinate.FromCoordinates(longitude, latitude), Tags = tags };
                            return node;
                        }
                    }
                    else if (rdr.Name == "way")
                    {
                        uint id = 0;
                        bool? visible = null;
                        for (int attr = 0; attr < rdr.AttributeCount; attr++)
                        {
                            rdr.MoveToAttribute(attr);
                            if (rdr.Name == "id")
                                if (rdr.Value.StartsWith("-"))
                                    id = (uint)(uint.MaxValue + int.Parse(rdr.Value));
                                else
                                    id = uint.Parse(rdr.Value);
                            else if (rdr.Name == "visible")
                                if (rdr.Value == "true")
                                    visible = true;
                                else
                                    visible = false;
                        }
                        List<uint> nodes = new List<uint>();
                        List<OSMTag> tags = new List<OSMTag>();
                        while (rdr.Read() && !(rdr.NodeType == XmlNodeType.EndElement && rdr.Name == "way"))
                        {
                            if (rdr.Name == "nd")
                            {
                                if (rdr.AttributeCount != 1)
                                    throw new Exception("wrong attribute count in nd element");

                                //uint refId = uint.Parse(rdr.GetAttribute(0));
                                uint refId;
                                if (rdr.GetAttribute(0).StartsWith("-"))
                                    refId = (uint)(uint.MaxValue + int.Parse(rdr.GetAttribute(0)));
                                else
                                    refId = uint.Parse(rdr.GetAttribute(0));
                                nodes.Add(refId);
                            }
                            else if (rdr.Name == "tag")
                            {
                                if (rdr.AttributeCount != 2)
                                    throw new Exception("wrong attribute count in tag element");
                                string key = null;
                                string value = null;
                                for (int attr = 0; attr < 2; attr++)
                                {
                                    rdr.MoveToAttribute(attr);
                                    if (rdr.Name == "k")
                                        key = rdr.Value;
                                    else if (rdr.Name == "v")
                                        value = rdr.Value;
                                }
                                tags.Add(new OSMTag() { Key = key, Value = value });
                            }
                        }
                        if (visible == null || visible == true)
                            return new OSMWay() { Id = id, Nodes = nodes, Tags = tags };
                    }
                    else if (rdr.Name == "relation")
                    {
                        uint id = 0;
                        bool? visible = null;
                        for (int attr = 0; attr < rdr.AttributeCount; attr++)
                        {
                            rdr.MoveToAttribute(attr);
                            if (rdr.Name == "id")
                                if (rdr.Value.StartsWith("-"))
                                    id = (uint)(uint.MaxValue + int.Parse(rdr.Value));
                                else
                                    id = uint.Parse(rdr.Value);
                            else if (rdr.Name == "visible")
                                if (rdr.Value == "true")
                                    visible = true;
                                else
                                    visible = false;
                        }
                        List<OSMMember> members = new List<OSMMember>();
                        List<OSMTag> tags = new List<OSMTag>();
                        while (rdr.Read() && !(rdr.NodeType == XmlNodeType.EndElement && rdr.Name == "relation"))
                        {
                            if (rdr.Name == "member")
                            {
                                if (rdr.AttributeCount != 3)
                                    throw new Exception("wrong attribute count in member element");
                                uint refId = 0;
                                OSMEntityType? type = null;
                                string role = null;
                                for (int attr = 0; attr < 3; attr++)
                                {
                                    rdr.MoveToAttribute(attr);
                                    if (rdr.Name == "type")
                                        if (rdr.Value == "way")
                                            type = OSMEntityType.Way;
                                        else if (rdr.Value == "node")
                                            type = OSMEntityType.Node;
                                        else if (rdr.Value == "relation")
                                            type = OSMEntityType.Relation;
                                        else
                                            throw new Exception("unknown member type: " + rdr.Value);
                                    else if (rdr.Name == "ref")
                                        if (rdr.Value.StartsWith("-"))
                                            refId = (uint)(uint.MaxValue + int.Parse(rdr.Value));
                                        else
                                            refId = uint.Parse(rdr.Value);
                                    else if (rdr.Name == "role")
                                        role = rdr.Value;
                                }
                                members.Add(new OSMMember() { Type = (OSMEntityType)type, Role = role, Ref = refId });
                            }
                            else if (rdr.Name == "tag")
                            {
                                if (rdr.AttributeCount != 2)
                                    throw new Exception("wrong attribute count in tag element");
                                string key = null;
                                string value = null;
                                for (int attr = 0; attr < 2; attr++)
                                {
                                    rdr.MoveToAttribute(attr);
                                    if (rdr.Name == "k")
                                        key = rdr.Value;
                                    else if (rdr.Name == "v")
                                        value = rdr.Value;
                                }
                                tags.Add(new OSMTag() { Key = key, Value = value });                                    
                            }
                        }
                        if (visible == null || visible == true)
                            return new OSMRelation() { Id = id, Tags = tags, Members = members };
                    }
                    else if (rdr.Name == "bounds")
                    {
                        double minLongitude = 0.0f;
                        double minLatitude = 0.0f;
                        double maxLongitude = 0.0f;
                        double maxLatitude = 0.0f; 
                        for (int attr = 0; attr < rdr.AttributeCount; attr++)
                        {
                            rdr.MoveToAttribute(attr);
                            if (rdr.Name == "minlat")
                                minLatitude = double.Parse(rdr.Value.Replace(".", ","));
                            else if (rdr.Name == "minlon")
                                minLongitude = double.Parse(rdr.Value.Replace(".", ","));
                            else if (rdr.Name == "maxlat")
                                maxLatitude = double.Parse(rdr.Value.Replace(".", ","));
                            else if (rdr.Name == "maxlon")
                                maxLongitude = double.Parse(rdr.Value.Replace(".", ","));
                        }
                        return new OSMBounds() { MaxLatitude = maxLatitude, MaxLongitude = maxLongitude, MinLatitude = minLatitude, MinLongitude = minLongitude };
                    }
                }
            }
            return null;
        }
    }
}
